/***************************************************************
 * Name:      minesweeperMain.cpp
 * Purpose:   Code for Application Frame
 * Author:    li ()
 * Copyright: li ()
 * License:
 **************************************************************/

#include "minesweeperMain.h"
#include <wx/msgdlg.h>
#include "icon/smile_face.xpm"
#include "icon/good_face.xpm"
#include "icon/bad_face.xpm"
#include <algorithm>
#include <thread>

//(*InternalHeaders(minesweeperFrame)
#include <wx/string.h>
#include <wx/intl.h>
//*)

#define PTR tilemap

//(*IdInit(minesweeperFrame)
const long minesweeperFrame::ID_PANEL2 = wxNewId();
const long minesweeperFrame::ID_BUTTON1 = wxNewId();
const long minesweeperFrame::ID_PANEL3 = wxNewId();
const long minesweeperFrame::ID_PANEL1 = wxNewId();
const long minesweeperFrame::ID_MENUITEM_easy = wxNewId();
const long minesweeperFrame::ID_MENUITEM_med = wxNewId();
const long minesweeperFrame::ID_MENUITEM_hard = wxNewId();
const long minesweeperFrame::ID_MENUITEM_mark = wxNewId();
const long minesweeperFrame::ID_MENUITEM_open = wxNewId();
//*)

BEGIN_EVENT_TABLE(minesweeperFrame,wxFrame)
    //(*EventTable(minesweeperFrame)
    //*)
END_EVENT_TABLE()

minesweeperFrame::minesweeperFrame(wxWindow* parent,wxWindowID id) {
    //(*Initialize(minesweeperFrame)
    wxMenuItem* MenuItem2;
    wxMenu* Menu1;
    wxMenuBar* MenuBar1;
    wxMenu* Menu2;

    Create(parent, id, _("mine"), wxDefaultPosition, wxDefaultSize, wxCAPTION|wxSYSTEM_MENU|wxCLOSE_BOX|wxMINIMIZE_BOX, _T("id"));
    BoxSizer1 = new wxBoxSizer(wxVERTICAL);
    BoxSizer2 = new wxBoxSizer(wxHORIZONTAL);
    mine_lcd = new LCD(this, ID_PANEL2, wxDefaultPosition, wxSize(39,23), wxTAB_TRAVERSAL, _T("ID_PANEL2"));
    BoxSizer2->Add(mine_lcd, 0, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 0);
    BoxSizer2->Add(-1,-1,1, wxALL|wxEXPAND, 0);
    newGame = new wxButton(this, ID_BUTTON1, wxEmptyString, wxDefaultPosition, wxSize(27,27), 0, wxDefaultValidator, _T("ID_BUTTON1"));
    BoxSizer2->Add(newGame, 0, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL|wxFIXED_MINSIZE, 0);
    BoxSizer2->Add(-1,-1,1, wxALL|wxEXPAND, 0);
    time_lcd = new LCD(this, ID_PANEL3, wxDefaultPosition, wxSize(39,23), wxTAB_TRAVERSAL, _T("ID_PANEL3"));
    BoxSizer2->Add(time_lcd, 0, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 0);
    BoxSizer1->Add(BoxSizer2, 0, wxTOP|wxLEFT|wxRIGHT|wxEXPAND, 5);
    tilemap = new Board(this, ID_PANEL1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("ID_PANEL1"));
    BoxSizer1->Add(tilemap, 1, wxALL|wxEXPAND, 5);
    SetSizer(BoxSizer1);
    MenuBar1 = new wxMenuBar();
    Menu1 = new wxMenu();
    Menu_easy = new wxMenuItem(Menu1, ID_MENUITEM_easy, _("easy"), wxEmptyString, wxITEM_RADIO);
    Menu1->Append(Menu_easy);
    Menu_med = new wxMenuItem(Menu1, ID_MENUITEM_med, _("medium"), wxEmptyString, wxITEM_RADIO);
    Menu1->Append(Menu_med);
    Menu_hard = new wxMenuItem(Menu1, ID_MENUITEM_hard, _("hard"), wxEmptyString, wxITEM_RADIO);
    Menu1->Append(Menu_hard);
    MenuBar1->Append(Menu1, _("Mode"));
    Menu3 = new wxMenu();
    Menu_mark = new wxMenuItem(Menu3, ID_MENUITEM_mark, _("auto mark"), wxEmptyString, wxITEM_CHECK);
    Menu3->Append(Menu_mark);
    Menu_open = new wxMenuItem(Menu3, ID_MENUITEM_open, _("auto open"), wxEmptyString, wxITEM_CHECK);
    Menu3->Append(Menu_open);
    MenuBar1->Append(Menu3, _("Assist"));
    Menu2 = new wxMenu();
    MenuItem2 = new wxMenuItem(Menu2, wxID_ABOUT, _("About\tF1"), _("Show info about this application"), wxITEM_NORMAL);
    Menu2->Append(MenuItem2);
    MenuBar1->Append(Menu2, _("Help"));
    SetMenuBar(MenuBar1);
    BoxSizer1->Fit(this);
    BoxSizer1->SetSizeHints(this);

    Connect(ID_BUTTON1,wxEVT_COMMAND_BUTTON_CLICKED,(wxObjectEventFunction)&minesweeperFrame::OnnewGameClick);
    tilemap->Connect(wxEVT_LEFT_DOWN,(wxObjectEventFunction)&minesweeperFrame::OntilemapLeftDown,0,this);
    tilemap->Connect(wxEVT_MIDDLE_DOWN,(wxObjectEventFunction)&minesweeperFrame::OntilemapMiddleDown,0,this);
    tilemap->Connect(wxEVT_RIGHT_DOWN,(wxObjectEventFunction)&minesweeperFrame::OntilemapRightDown,0,this);
    Connect(ID_MENUITEM_easy,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&minesweeperFrame::OnMenu_ModeSelected);
    Connect(ID_MENUITEM_med,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&minesweeperFrame::OnMenu_ModeSelected);
    Connect(ID_MENUITEM_hard,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&minesweeperFrame::OnMenu_ModeSelected);
    Connect(ID_MENUITEM_mark,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&minesweeperFrame::OnMenu_markSelected);
    Connect(ID_MENUITEM_open,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&minesweeperFrame::OnMenu_openSelected);
    Connect(wxID_ABOUT,wxEVT_COMMAND_MENU_SELECTED,(wxObjectEventFunction)&minesweeperFrame::OnAbout);
    //*)
    map_timer.SetOwner(tilemap, Board::timerId);
    num_timer.SetOwner(time_lcd, LCD::timerId);
    auto_mark = false;
    auto_open = false;
    initMode(EASY);
}

minesweeperFrame::~minesweeperFrame() {
    //(*Destroy(minesweeperFrame)
    //*)
}

void minesweeperFrame::OnAbout(wxCommandEvent& event) {
    wxString msg = _("readme");
    wxMessageBox(msg, _("Welcome to..."));
}

void minesweeperFrame::OnMenu_ModeSelected(wxCommandEvent& event) {
    initMode(event.GetId() - ID_MENUITEM_easy);
}

void minesweeperFrame::initMode(int m) {
    switch (m) {
    case EASY:
        mine_num = 10;
        resize(9,9);
        break;
    case MEDIUM:
        mine_num = 40;
        resize(16,16);
        break;
    case HARD:
        mine_num = 99;
        resize(30,16);
        break;

    default:
        break;
    }
    after_initMode_();
    initGame();
}

void minesweeperFrame::resize(int width, int height) {
    auto wpt = tilemap->GetPosition();
#ifdef __WXMSW__
    const int detW = wpt.x+11;
    const int detH = wpt.y+54;
#elif __WXGTK__
    const int detW = wpt.x+5;
    const int detH = wpt.y+32;
#endif
    tilemap->resize(height,width);
    SetSize(width*TILESIZE+detW,height*TILESIZE+detH);
    Layout();
}

void minesweeperFrame::initGame() {
    num_timer.Stop();
    map_timer.Stop();

    FOREACH(pt, ele) {
        ele.outer = BLANK;
        ele.inner = 0;
    }

    state = BEGIN;
    left_mine = mine_num;
    left_blank = tilemap->size()-mine_num;
    tilemap->wrong_mine.clear();
    q_click.clear();
    q_0.clear();
    q_posi.clear();

    newGame->SetBitmap({smile_face});
    tilemap->Refresh();
    time_lcd->setNum(0);
    mine_lcd->setNum(mine_num);

    after_initGame_();
}

void minesweeperFrame::OnnewGameClick(wxCommandEvent& event) {
    initGame();
}

void minesweeperFrame::OntilemapLeftDown(wxMouseEvent& event) {
    if(state == END) return;
    Point pt{event.GetPosition()};
    auto &t = tilemap->at(pt);
    if (t.outer == BLANK) {
        Open(pt);
    } else if (t.outer == OPEN && t.inner > 0) {
        multiOpen(pt);
    }
}

void minesweeperFrame::OntilemapRightDown(wxMouseEvent& event) {
    if(state == END) return;
    Point pt{event.GetPosition()};
    Mark(pt);
}

void minesweeperFrame::Open(const Point &pt) {
    if (state == BEGIN) {
        firstClick(pt);
    }
    q_click.push_back(pt);
    for (auto it = q_click.begin(); it !=  q_click.end(); ++it) {
        auto t = tilemap->at(*it);
        if (t.outer == BLANK) {
            if (t.inner == MINE) {
                fail(*it);
                return;
            }
            _Open(*it);
            if (t.inner == 0) {
                q_0.push_back(*it);
            } else {
                q_posi.push_back(*it);
            }
            for (auto j = q_0.begin(); j != q_0.end(); ++j) {
                AROUND(*j, pt1, ele) {
                    if (ele.outer == BLANK) {
                        _Open(pt1);
                        if (ele.inner == 0) {
                            q_0.push_back(pt1);
                        } else {
                            q_posi.push_back(pt1);
                        }
                    }
                }
            }
            if (left_blank == 0) {
                success();
                return;
            }
            q_0.clear();
            autoMark_after_click_();
            autoOpen_after_click_();
            update_chance_();
        }
    }
    q_click.clear();
}

void minesweeperFrame::Mark(const Point &pt) {
    auto &t = tilemap->at(pt);
    if (t.outer == BLANK) {
        if (left_mine > 0) {
            _Mark(pt);
            mine_lcd->setNum(left_mine);
            autoOpen_after_Mark_(pt);
        }
    } else if (t.outer == FLAG) {
        t.outer = BLANK;
        tilemap->drawTile(pt,BLANK);
        mine_lcd->setNum(++left_mine);
    }
}

void minesweeperFrame::multiOpen(const Point &pt) {
    auto &t = tilemap->at(pt);
    if (t.outer != OPEN)
        return;
    int flag = 0;
    AROUND(pt, pt1, ele) {
        if (ele.outer == BLANK) {
            q_click.push_back(pt1);
        }
        if (ele.outer == FLAG) {
            flag++;
        }
    }
    if (t.inner == flag) {
        if (q_click.size() > 0) {
            auto pos = q_click.front();
            q_click.pop_front();
            Open(pos);
        }
    } else {
        Blink();
    }
}

void minesweeperFrame::firstClick(const Point &pt) {
    num_timer.Start(1000);
    state = RUNNING;
    tilemap->initMine(mine_num, pt);
}

void minesweeperFrame::_Open(const Point &pt) {
    --left_blank;
    auto &t = tilemap->at(pt);
    t.outer = OPEN;
    tilemap->drawTile(pt,t.inner);
}

void minesweeperFrame::_Mark(const Point &pt) {
    --left_mine;
    tilemap->at(pt).outer = FLAG;
    tilemap->drawTile(pt,FLAG);
}

void minesweeperFrame::Blink() {
    for (auto & pt: q_click) {
        tilemap->drawTile(pt,0);
    }
    std::vector<Point> arr(q_click.begin(), q_click.end());
    q_click.clear();
    std::thread([this, arr]() {
        std::this_thread::sleep_for(std::chrono::milliseconds(120));
        for (auto & pt: arr) {
            tilemap->drawTile(pt,BLANK);
        }
    }).detach();
}

void minesweeperFrame::fail(const Point &pt) {
    tilemap->at(pt) = {OPEN,BOMB};
    tilemap->drawTile(pt, BOMB);
    num_timer.Stop();
    state = END;
    newGame->SetBitmap({bad_face});

    FOREACH(pt1, ele) {
        if (ele.outer == BLANK && ele.inner == MINE) {
            tilemap->drawTile(pt1,MINE);
        } else if (ele.outer == FLAG && ele.inner != MINE) {
            tilemap->wrong_mine.push_back(pt1);
        }
    }
    if (tilemap->wrong_mine.size() > 0) {
        map_timer.Start(800);
    }
}

void minesweeperFrame::success() {
    mine_lcd->setNum(mine_num);
    num_timer.Stop();
    state = END;
    newGame->SetBitmap({good_face});

    FOREACH(pt, ele) {
        if (ele.outer == BLANK && ele.inner == MINE) {
            _Mark(pt);
        }
    }
}

void minesweeperFrame::after_initMode_() {
    coe = tilemap->size() / mine_num * 19.5;
}

void minesweeperFrame::after_initGame_() {
    chance = 0;
    use_chance = 0;
}

void minesweeperFrame::OnMenu_markSelected(wxCommandEvent& event) {
    auto_mark = !auto_mark;
}

void minesweeperFrame::autoMark_after_click_() {
    if (auto_mark) {
        for (auto j = q_posi.begin(); j != q_posi.end();) {
            Stats c = tilemap->Count(*j);
            if (c.blank + c.flag == tilemap->at(*j).inner) {
                if (c.blank > 0) {
                    AROUND(*j, pt1, ele) {
                        if (ele.outer == BLANK) {
                            _Mark(pt1);
                        }
                    }
                }
                j = q_posi.erase(j);
            } else {
                ++j;
            }
        }
        mine_lcd->setNum(left_mine);
    }
}

void minesweeperFrame::OnMenu_openSelected(wxCommandEvent& event) {
    auto_open = !auto_open;
}

void minesweeperFrame::autoOpen_after_click_() {
    if (auto_open) {
        for (auto j = q_posi.begin(); j != q_posi.end();) {
            Stats c = tilemap->Count(*j);
            if (c.flag == tilemap->at(*j).inner) {
                if (c.blank > 0) {
                    AROUND(*j, pt1, ele) {
                        if (ele.outer == BLANK) {
                            q_click.push_back(pt1);
                        }
                    }
                }
                j = q_posi.erase(j);
            } else {
                ++j;
            }
        }
    }
}

void minesweeperFrame::autoOpen_after_Mark_(const Point &pt) {
    if (auto_open) {
        AROUND(pt, pt1, ele) {
            if (state != END && ele.outer == OPEN && ele.inner > 0) {
                Stats c = tilemap->Count(pt1);
                if (c.blank > 0 && c.flag == ele.inner) {
                    multiOpen(pt1);
                }
            }
        }
    }
}

void minesweeperFrame::OntilemapMiddleDown(wxMouseEvent& event) {
    if (chance <= use_chance || state == END)
        return;
    Point pt{event.GetPosition()};
    auto n = time_lcd->num%5;
    std::vector<Point> arr;
    FOREACH(pt, ele) {
        if (ele.outer == BLANK && ele.inner == n) {
            arr.push_back(pt);
        }
    }
    use_chance++;
    if (arr.size() > 0) {
        auto pos = std::min_element(arr.begin(),arr.end(),[&pt](const Point &a, const Point &b) {
            return std::hypot(a.x-pt.x,a.y-pt.y) < std::hypot(b.x-pt.x,b.y-pt.y);
        });
        Open(*pos);
    } else {
        update_chance_();
    }
}

void minesweeperFrame::update_chance_() {
    chance = (tilemap->size() - mine_num - left_blank) / coe;
    mine_lcd->SetToolTip(wxString::Format("%d",chance-use_chance));
}
